package io.xiyou.core.lock;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Order(1)
@Aspect
@Component
public class DLockAspect {

    private final static String LOCK_PREFIX = "DLOCK:";

    /**
     * 方法参数名解析
     */
    private final static LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();

    @Resource
    private RedissonClient redissonClient;

    @Around(value = "@annotation(dLock)", argNames = "proceedingJoinPoint,dLock")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, DLock dLock) throws Throwable {
        return doLock(proceedingJoinPoint, dLock);
    }

    private Object doLock(ProceedingJoinPoint proceedingJoinPoint, DLock dLock) throws Throwable {
        /* 获取拦截方法 参数 */
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        Object[] params = proceedingJoinPoint.getArgs();

        /* 创建上下文 */
        String[] paraNameArray = parameterNameDiscoverer.getParameterNames(method);
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (paraNameArray != null) {
            for (int i = 0; i < paraNameArray.length; i++) {
                context.setVariable(paraNameArray[i], params[i]);
            }
        }

        /* 创建解析器 */
        ExpressionParser parser = new SpelExpressionParser();

        /* 解析锁 */
        List<RLock> rLockList = new ArrayList<>(dLock.value().length);
        for (String oriLockString : dLock.value()) {
            String lockString = parser.parseExpression(oriLockString, new TemplateParserContext()).getValue(context, String.class);
            rLockList.add(redissonClient.getFairLock(LOCK_PREFIX + lockString));
        }

        /* 加锁数量 */
        int lockNumber = 0;

        try {
            /* 尝试加锁 */
            for (RLock lock : rLockList) {
                boolean isLock = false;

                try {
                    isLock = lock.tryLock(dLock.timeOut(), dLock.lockTime(), TimeUnit.SECONDS);
                } catch (InterruptedException ie) {
                    log.error(ie.getMessage(), ie);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }

                /* 加锁失败 */
                if (!isLock) {
                    throw new Exception("加锁失败！");
                }

                lockNumber++;
            }

            /* 执行业务 */
            return proceedingJoinPoint.proceed();
        } finally {
            /* 解锁 */
            if (lockNumber > 0) {
                /* 解锁数量 */
                int unLockNumber = 0;

                for (RLock lock : rLockList) {
                    try {
                        lock.unlock();
                    } catch (IllegalMonitorStateException imse) {
                        log.error(imse.getMessage(), imse);
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                    unLockNumber++;

                    /* 解锁到未加锁部分 */
                    if (unLockNumber >= lockNumber) {
                        break;
                    }
                }
            }
        }
    }
}
